package com.hongshu.web.service;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hongshu.common.core.constant.NoteConstant;
import com.hongshu.common.core.domain.NoteSearchVo;
import com.hongshu.common.core.enums.AuditStatusEnum;
import com.hongshu.common.core.utils.bean.BeanUtils;
import com.hongshu.web.domain.entity.WebLikeOrCollection;
import com.hongshu.web.domain.entity.WebNote;
import com.hongshu.web.domain.entity.WebUser;
import com.hongshu.web.mapper.web.WebLikeOrCollectionMapper;
import com.hongshu.web.mapper.web.WebNoteMapper;
import com.hongshu.web.mapper.web.WebUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.mahout.cf.taste.impl.model.jdbc.MySQLJDBCDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;


/**
 * ES
 *
 * @author: hongshu
 */
@Service
@Slf4j
public class RecommendNoteServiceImpl extends ServiceImpl<WebLikeOrCollectionMapper, WebLikeOrCollection> implements IRecommendNoteService {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private WebUserMapper userMapper;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private WebNoteMapper noteMapper;
    @Autowired
    private WebLikeOrCollectionMapper likeOrCollectionMapper;


    /**
     * 推荐笔记
     *
     * @param userId 用户ID
     */
    @Override
    public List<NoteSearchVo> getRecommendNote(long userId) {
        List<NoteSearchVo> noteSearchVoList;
        try {
            // Step 1: 创建数据模型：使用 MySQLJDBCDataModel 从数据库表加载数据。
            // 有一个表 web_like_or_collection，存储用户对笔记的交互数据。
            DataModel model = new MySQLJDBCDataModel(
                    dataSource,
                    "web_like_or_collection",  // Table name
                    "uid",                      // User ID column
                    "like_or_collection_id",    // Blog ID column
                    "type",                     // Rating column (Here we use interaction type as rating)
                    null                       // Timestamp column (optional)
            );
            // Step 2: 计算相似度：使用皮尔逊相关系数计算用户之间的相似度。未中心化的余弦相似度
            UserSimilarity similarity = new UncenteredCosineSimilarity(model);
            // Step 3: 定义邻居：使用 NearestNUserNeighborhood 定义用户邻居。
            NearestNUserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model);
            //  Step 4: 创建推荐器：基于用户的相似度，创建基于用户的推荐器。
            Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
            // Step 5: 生成推荐：为指定用户生成推荐笔记列表。
            List<RecommendedItem> recommendations = recommender.recommend(userId, 10);
            List<Long> recommendedNoteIds = recommendations.stream()
                    .map(RecommendedItem::getItemID)
                    .collect(Collectors.toList());
            // Step 6: 获取推荐笔记详情：从 Elasticsearch 中获取推荐笔记。
            SearchRequest searchRequest = this.buildSearchRequest(recommendedNoteIds);
            // Step 7: 筛选出所需的数据。
            SearchResponse<NoteSearchVo> searchResponse = elasticsearchClient.search(searchRequest, NoteSearchVo.class);
            noteSearchVoList = searchResponse.hits().hits().stream()
                    .map(Hit::source)
                    .collect(Collectors.toList());
            // 随机排序
            Collections.shuffle(noteSearchVoList, new Random());
        } catch (Exception e) {
            log.error("获取推荐笔记时发生异常：", e);
            return Collections.emptyList();
        }
        return noteSearchVoList;
    }

    /**
     * 推荐用户
     *
     * @param userId 用户ID
     */
    @Override
    public List<WebUser> getRecommendUser(long userId) {
        List<WebUser> userList;
        try {
            // Step 1: 创建数据模型：使用 MySQLJDBCDataModel 从数据库表加载数据。
            // 有一个表 web_follower，存储用户关注关系数据。
            DataModel model = new MySQLJDBCDataModel(
                    dataSource,
                    "web_follower",  // Table name
                    "uid",                      // User ID column
                    "fid",                      // FollowUser ID column
                    "type",                     // Rating column (Here we use interaction type as rating)
                    null                       // Timestamp column (optional)
            );
            // Step 2: 计算相似度：使用皮尔逊相关系数计算用户之间的相似度。未中心化的余弦相似度
            UserSimilarity similarity = new UncenteredCosineSimilarity(model);
            // Step 3: 定义邻居：使用 NearestNUserNeighborhood 定义用户邻居。
            NearestNUserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model);
            //  Step 4: 创建推荐器：基于用户的相似度，创建基于用户的推荐器。
            Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
            // Step 5: 生成推荐：为指定用户生成推荐用户列表。
            List<RecommendedItem> recommendations = recommender.recommend(userId, 10);
            List<Long> recommendedUserIds = recommendations.stream()
                    .map(RecommendedItem::getItemID)
                    .collect(Collectors.toList());
            // Step 6: 获取推荐笔记详情：从 Elasticsearch 中获取推荐笔记。
//            SearchRequest searchRequest = this.buildSearchRequest(recommendedUserIds);
            if (CollectionUtil.isNotEmpty(recommendedUserIds)) {
                userList = userMapper.selectBatchIds(recommendedUserIds);
            } else {
                userList = userMapper.selectList(new QueryWrapper<>());
            }
            // Step 7: 筛选出所需的数据。
//            SearchResponse<WebUser> searchResponse = elasticsearchClient.search(searchRequest, WebUser.class);
//            userList = searchResponse.hits().hits().stream()
//                    .map(Hit::source)
//                    .collect(Collectors.toList());
            // 随机排序
            Collections.shuffle(userList, new Random());
        } catch (Exception e) {
            log.error("获取推荐用户时发生异常：", e);
            return Collections.emptyList();
        }
        return userList;
    }

    /**
     * 获取推荐笔记列表
     */
    private SearchRequest buildSearchRequest(List<Long> recommendedNoteIds) {
        // 推荐列表为空，默认随机加载100条数据
        if (CollectionUtil.isEmpty(recommendedNoteIds)) {
            return SearchRequest.of(s -> s
                    .index(NoteConstant.NOTE_INDEX)
                    .size(100)
            );
        } else {
            // 推荐列表不为空，加载推荐数据
            List<FieldValue> fieldValues = recommendedNoteIds.stream()
                    .map(FieldValue::of)
                    .collect(Collectors.toList());
            return SearchRequest.of(s -> s
                    .index(NoteConstant.NOTE_INDEX)
                    .query(q -> q
                            .terms(t -> t
                                    .field("id")
                                    .terms(terms -> terms.value(fieldValues))
                            )
                    )
                    .size(recommendedNoteIds.size())
            );
        }
    }

    /**
     * 从MySQL获取推荐笔记
     *
     * @param userId      用户ID
     * @param currentPage 当前页
     * @param pageSize    页大小
     * @return 分页结果
     */
    @Override
    public Page<NoteSearchVo> getRecommendNoteFromMysql(String userId, long currentPage, long pageSize) {
        Page<NoteSearchVo> page = new Page<>(currentPage, pageSize);

        // 获取用户点赞和收藏的笔记ID列表
        List<String> likedOrCollectedNoteIds = likeOrCollectionMapper.selectList(
                        new QueryWrapper<WebLikeOrCollection>()
                                .eq("uid", userId)
                                .in("type", Arrays.asList(1, 3)) // 1：点赞图片 3：收藏图片
                                .select("like_or_collection_id")
                ).stream()
                .map(WebLikeOrCollection::getLikeOrCollectionId)
                .collect(Collectors.toList());

        // 获取用户点赞/收藏的笔记的作者ID列表
        List<String> authorIds = Collections.emptyList();
        if (!likedOrCollectedNoteIds.isEmpty()) {
            authorIds = noteMapper.selectList(
                            new QueryWrapper<WebNote>()
                                    .in("id", likedOrCollectedNoteIds)
                                    .select("DISTINCT uid")
                    ).stream()
                    .map(WebNote::getUid)
                    .collect(Collectors.toList());
        }

        // 获取用户点赞/收藏的笔记的分类ID列表
        List<String> categoryIds = Collections.emptyList();
        if (!likedOrCollectedNoteIds.isEmpty()) {
            categoryIds = noteMapper.selectList(
                            new QueryWrapper<WebNote>()
                                    .in("id", likedOrCollectedNoteIds)
                                    .select("DISTINCT cpid")
                    ).stream()
                    .map(WebNote::getCpid)
                    .collect(Collectors.toList());
        }

        // 构建查询条件
        QueryWrapper<WebNote> queryWrapper = new QueryWrapper<WebNote>()
                .eq("audit_status", AuditStatusEnum.PASS.getCode());

        // 如果有用户行为数据，基于用户偏好推荐
        if (!authorIds.isEmpty() || !categoryIds.isEmpty()) {
            List<String> finalAuthorIds = authorIds;
            List<String> finalCategoryIds = categoryIds;
            queryWrapper.and(wrapper -> {
                if (!finalAuthorIds.isEmpty()) {
                    wrapper.or().in("uid", finalAuthorIds);
                }
                if (!finalCategoryIds.isEmpty()) {
                    wrapper.or().in("cid", finalCategoryIds);
                }
            });
        }

        // 排除用户已经点赞/收藏的笔记
        if (!likedOrCollectedNoteIds.isEmpty()) {
            queryWrapper.notIn("id", likedOrCollectedNoteIds);
        }

        // 排除用户自己的笔记
        queryWrapper.ne("uid", userId);

        // 按点赞数、收藏数、创建时间排序
        queryWrapper.orderByDesc("like_count")
                .orderByDesc("collection_count")
                .orderByDesc("update_time");

        // 执行分页查询
        Page<WebNote> notePage = noteMapper.selectPage(new Page<>(currentPage, pageSize), queryWrapper);

        // 转换结果
        page.setCurrent(notePage.getCurrent());
        page.setSize(notePage.getSize());
        page.setTotal(notePage.getTotal());
        page.setRecords(notePage.getRecords().stream()
                .map(this::convertToSearchVo)
                .collect(Collectors.toList()));

        return page;
    }

    /**
     * 将WebNote实体转换为NoteSearchVo
     */
    private NoteSearchVo convertToSearchVo(WebNote note) {
        NoteSearchVo vo = new NoteSearchVo();
        BeanUtils.copyProperties(note, vo);
        return vo;
    }
}
